#pragma once

#include "compiler.hpp"
#include "runner.hpp"
#include "../comm/log.hpp"
#include "../comm/util.hpp"
#include <jsoncpp/json/json.h>
#include <signal.h>
#include <unistd.h>

namespace ns_compile_and_run
{
    using namespace ns_log;
    using namespace ns_util;
    using namespace ns_compiler;
    using namespace ns_runner;

    class CompileAndRun
    {
    public:
        // 将信号编号转成出错原因
        // status > 0: 进程收到信号然后崩溃
        // status < 0: 非运行时出错
        // status = 0: 整个过程(编译 + 运行)全部完成
        static std::string StatusToDesc(int status)
        {
            std::string desc;
            switch(status)
            {
                case -1:
                    desc = "用户提交的代码为空";
                    break;
                case -2:
                    // 服务端出现了形成源文件失败或尝试运行程序时出错
                    // 这些错误不需要呈现给用户
                    desc = "出现未知错误";
                    break;
                // case -3:
                //     desc = "编译出错";
                //     break; 
                case 0:
                    desc = "编译并运行成功";
                    break;
                case SIGABRT: // 6 号信号
                    desc = "使用的内存资源超出限制";
                    break;
                case SIGFPE: // 8 号信号
                    desc = "浮点数异常";
                    break;
                case SIGSEGV: // 11 号信号
                    desc = "发生段错误";
                    break;
                case SIGXCPU: // 24 号信号
                    desc = "使用的 CPU 资源超出限制";
                    break;
                default:
                    desc = "未知状态码: " + std::to_string(status); 
                    break;
            }
            return desc;
        }

        // 清理临时文件
        static void RemoveTempFile(const std::string& file_name)
        {
            std::string _src = PathUtil::Src(file_name);
            if(FileUtil::IsFileExists(_src)) unlink(_src.c_str());

            std::string _exe = PathUtil::Exe(file_name);
            if(FileUtil::IsFileExists(_exe)) unlink(_exe.c_str());

            std::string _complier_error = PathUtil::CompilerError(file_name);
            if(FileUtil::IsFileExists(_complier_error)) unlink(_complier_error.c_str());

            std::string _stdin = PathUtil::Stdin(file_name);
            if(FileUtil::IsFileExists(_stdin)) unlink(_stdin.c_str());

            std::string _stdout = PathUtil::Stdout(file_name);
            if(FileUtil::IsFileExists(_stdout)) unlink(_stdout.c_str());

            std::string _stderr = PathUtil::Stderr(file_name);
            if(FileUtil::IsFileExists(_stderr)) unlink(_stderr.c_str());
        }

        /********************************************
         * 输入: 
         * code:  用户提交的代码
         * input: 用户提交的代码所对应的输入, 该字段不进行处理
         * cpu_limit: 时间要求
         * mem_limit: 空间要求
         * 
         * 输出:
         * 必填字段
         * status: 状态码
         * reason: 出错原因
         * 选填字段
         * stdout: 程序运行完的输出结果
         * stderr: 程序运行完的错误结果
         * 输入型参数 in_json: {"code":"#include...", "input":"", "cpu_limit":3, "mem_limit":10240}
         * 输出型参数 out_json: {"status":0, "reason":"", "stdout":"...", "stderr":"..."}
        ********************************************/
        static void Start(const std::string& in_json, std::string* out_json)
        {
            Json::Value in_value;
            Json::Reader reader;
            reader.parse(in_json, in_value); // 反序列化

            std::string code = in_value["code"].asString();    // 用户提交的代码
            std::string input = in_value["input"].asString();  // 用户代码的输入, 不进行处理
            int cpu_limit = in_value["cpu_limit"].asInt();     // CPU 资源限制
            int mem_limit = in_value["mem_limit"].asInt();     // 内存资源限制

            Json::Value out_value;
            int status = 0; // 表示错误原因的状态码
            int ret = 0;    // 保存运行的结果
            std::string file_name; // 唯一文件名

            if(code.size() == 0) // 用户没有写代码
            {
                status = -1;
                goto END;
            }

            // 形成唯一文件名(没有路径和后缀)
            // 文件名: 毫秒级时间戳 + 原子性递增的唯一值
            file_name = FileUtil::UniqueFileName();
            if(!FileUtil::WriteFile(PathUtil::Src(file_name), code)) // 形成临时源文件, 其保存在 temp 目录下
            {
                status = -2;
                LOG(ERROR) << "形成源文件失败" << "\n";
                // 形成源文件失败, 该错误原因不需要呈现给用户
                goto END;
            }

            if(!Compiler::Compile(file_name)) // 对源代码进行编译
            {
                // 代码编译时出现错误
                status = -3;
                LOG(INFO) << "用户的代码编译失败" << "\n";
                goto END;
            }

            if(FileUtil::IsFileExists(PathUtil::Exe(file_name))) // 判断是否形成可执行程序
            {
                ret = Runner::Run(file_name, cpu_limit, mem_limit); // 执行程序
                if(ret < 0)
                {
                    // 尝试运行可执行程序的过程中发生了错误
                    LOG(ERROR) << "运行用户的代码时出现了错误" << "\n";
                    status = -2;
                }
                else if(ret > 0)
                {
                    // 运行用户的代码时出现了异常
                    LOG(ERROR) << "运行用户的代码时收到了信号" << "\n";
                    status = ret; // 此时 ret 是信号编号
                }
                else
                {
                    // 运行成功
                    status = 0;
                }
            }

            // 统一在这里进行差错处理
            END:
                // 设置状态码
                out_value["status"] = status;

                if(status == -3) // 编译报错, 读取编译错误信息
                {
                    std::string desc;
                    FileUtil::ReadFile(PathUtil::CompilerError(file_name), &desc, true);
                    out_value["reason"] = desc;
                }
                else // 将状态码转换成描述信息
                    out_value["reason"] = StatusToDesc(status);

                // 整个过程(编译 + 运行)全部成功
                if(status == 0) 
                {
                    // _stdout 和 _stderr 为输出型参数
                    std::string _stdout;
                    std::string _stderr;
                    FileUtil::ReadFile(PathUtil::Stdout(file_name), &_stdout, true);
                    FileUtil::ReadFile(PathUtil::Stderr(file_name), &_stderr, true);
                    out_value["stdout"] = _stdout;
                    out_value["stderr"] = _stderr;
                }

                // 序列化
                Json::StyledWriter writer;
                *out_json = writer.write(out_value);

                RemoveTempFile(file_name);
        }
    };
}